package system.ui;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.JRadioButton;
import javax.swing.ButtonGroup;
import javax.swing.SpinnerNumberModel;
import javax.swing.JSpinner;
import javax.swing.BoxLayout;
import javax.swing.SpringLayout;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.awt.TextField;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import system.train.ConcreteTrain;
import system.track.GenericBlock;
import system.track.Direction;
import system.track.Track;
import system.communication.Message;
import system.communication.MessageArg;
import system.communication.HandControlPoller;
import system.communication.MessageListener;
import system.communication.ConcreteMonitor;

/**
 * The Gui sets up the visible control panel for the Train program, but it also
 * directs the "Engine"; it instantiates and prepares the system.
 * 
 * @author  Erin Blockley
 * @version Cycle 1 - complete
 */
public class Gui extends JFrame implements ActionListener, ChangeListener
{
    // instantiating all variables pertaining to the control panel
    private Container contents;
    private JPanel mainArea, topArea, bottomArea, trainArea, hc1Area, hc2Area, sensorArea, switchArea, routeArea;
    private JLabel goStopLabel, speedLabel, hc1Label, hc2Label, sensorLabel, switchLabel, routeLabel;
    private JToggleButton goStopButton, hc1Button, hc2Button;
    private JRadioButton routeCW, routeCCW;
    private ButtonGroup routeGroup;
    private SpinnerNumberModel model;
    private JSpinner speedSpinner;
    private TextArea sensorText;
    private TextField switch1, switch2, switch3, switch4, switch5, switch6;
    private Font font1;

    // instantiating all variables pertaining to the Engine
    private Track myTrack;
    private ConcreteTrain train;
    private String routeDir = "CW";
    private boolean running = false;
    private MessageListener mlistener;
    public HandControlPoller myPoller;
    private GenericBlock startBlock;
    public static int whichHandControl = 0;
    public static boolean handControlsOn = false;
    public static Gui instance = null;

    /**
     * Singleton method which either calls the Constructor or returns an
     * instance of the current Gui.  Used by the Listener, which needs to 
     * pull variables from the Gui.
     * 
     * @return  the instance of the Gui object in memory
     */
    public static Gui getInstance()
    {
        if (instance == null)
        {
            instance = new Gui();
        }
        return instance;
    }

    /**
     * Constructor for Objects of Class Gui.
     */
    private Gui()
    {
        super("Control Panel");
        
        // setting up the basic window
        contents = getContentPane();
        contents.setLayout(new FlowLayout());
        setSize(700,500);
        font1 = new Font("Verdana", Font.PLAIN, 14);
        
        // subpanels within the main panel
        mainArea = new JPanel(); //contains the top & bottom panels
        mainArea.setLayout(new BorderLayout(20,20));
        topArea = new JPanel(); //contains the upper panels
        topArea.setLayout(new GridLayout(1,3));
        bottomArea = new JPanel(); //contains the lower panels
        bottomArea.setLayout(new GridLayout(1,3));
        trainArea = new JPanel(); //contains the train controls
        trainArea.setLayout(new BoxLayout(trainArea,3));
        hc1Area = new JPanel(); //contains indicators for Hand Control 1
        hc1Area.setLayout(new FlowLayout());
        hc2Area = new JPanel(); //contains indicators for Hand Control 2
        hc2Area.setLayout(new FlowLayout());
        sensorArea = new JPanel(); //contains sensor text messages panel
        sensorArea.setLayout(new BoxLayout(sensorArea,3));
        switchArea = new JPanel(); //contains switch indicators panel
        switchArea.setLayout(new BoxLayout(switchArea,BoxLayout.Y_AXIS));
        routeArea = new JPanel(); //contains route selection panel
        routeArea.setLayout(new BoxLayout(routeArea,BoxLayout.Y_AXIS));

        // the button which starts and stops the train
        goStopButton = new JToggleButton("Running");
        goStopButton.setFont(font1);
        goStopLabel = new JLabel("Start the train:");
        goStopLabel.setLabelFor(goStopButton);
        goStopLabel.setFont(font1);

        // the spinner which sets the train's speed
        SpinnerNumberModel model = new SpinnerNumberModel(4,1,7,1);
        speedSpinner = new JSpinner(model);
        speedSpinner.setFont(font1);
        speedLabel = new JLabel("Train speed:");
        speedLabel.setLabelFor(speedSpinner);
        speedLabel.setFont(font1);
        trainArea.add(goStopLabel);
        trainArea.add(goStopButton);
        trainArea.add(speedLabel);
        trainArea.add(speedSpinner);

        // the toggle for Hand Controller 1
        hc1Label = new JLabel("Hand Controller 1");
        hc1Label.setFont(font1);
        hc1Button = new JToggleButton("Use HC1");
        hc1Button.setFont(font1);
        hc1Area.add(hc1Label);
        hc1Area.add(hc1Button);
        
        // the toggle for Hand Controller 2
        hc2Label = new JLabel("Hand Controller 2");
        hc2Label.setFont(font1);
        hc2Button = new JToggleButton("Use HC2");
        hc2Button.setFont(font1);
        hc2Area.add(hc2Label);
        hc2Area.add(hc2Button);
        
        // sensor messages are posted here
        sensorText = new TextArea("Not yet started...\n",20,15,3);
        sensorLabel = new JLabel("Sensors Passing:     ");
        sensorLabel.setLabelFor(sensorText);
        sensorLabel.setFont(font1);
        sensorLabel.setHorizontalAlignment(JLabel.LEFT);
        sensorArea.add(sensorLabel);
        sensorArea.add(sensorText);

        // indicators for all switches
        switchLabel = new JLabel("Switches");
        switchLabel.setFont(font1);
        switch1 = new TextField("Switch 1:  ");
        switch1.setFont(font1);
        switch2 = new TextField("Switch 2:  ");
        switch2.setFont(font1);
        switch3 = new TextField("Switch 3:  ");
        switch3.setFont(font1);
        switch4 = new TextField("Switch 4:  ");
        switch4.setFont(font1);
        switch5 = new TextField("Switch 5:  ");
        switch5.setFont(font1);
        switch6 = new TextField("Switch 6:  ");
        switch6.setFont(font1);
        switchArea.add(switchLabel);
        switchArea.add(switch1);
        switchArea.add(switch2);
        switchArea.add(switch3);
        switchArea.add(switch4);
        switchArea.add(switch5);
        switchArea.add(switch6);

        // the radio buttons for selecting the train's route
        routeLabel = new JLabel("Routes");
        routeLabel.setFont(font1);
        routeCW = new JRadioButton("CW demo route");
        routeCW.setActionCommand("routeCW");
        routeCW.setSelected(true);
        routeCW.setFont(font1);
        routeCCW = new JRadioButton("CCW demo route");
        routeCCW.setActionCommand("routeCCW");
        routeCCW.setFont(font1);
        routeGroup = new ButtonGroup();
        routeGroup.add(routeCW);
        routeGroup.add(routeCCW);
        routeArea.add(routeLabel);
        routeArea.add(routeCW);
        routeArea.add(routeCCW);

        // panel consolidation
        topArea.add(trainArea);
        topArea.add(hc1Area);
        topArea.add(hc2Area);
        bottomArea.add(sensorArea);
        bottomArea.add(switchArea);
        bottomArea.add(routeArea);
        mainArea.add(topArea, BorderLayout.NORTH);
        mainArea.add(bottomArea, BorderLayout.CENTER);
        contents.add(mainArea);
        
        setVisible(true);
        
        // event listeners set
        goStopButton.addActionListener(this);
        speedSpinner.addChangeListener(this);
        hc1Button.addActionListener(this);
        hc2Button.addActionListener(this);
        routeCW.addActionListener(this);
        routeCCW.addActionListener(this);

        //mlistener = ConcreteMonitor.getInstance().getListener();
        //mlistener.setName("listener");
        //mlistener.setPriority(9);
        //mlistener.start();
        
        // setting up the Engine Objects
        myTrack = new Track();
        train = new ConcreteTrain(myTrack.getStartPoint(1));
        train.setDirection(system.track.Direction.CLOCKWISE);
        train.setRoute(system.track.Direction.CLOCKWISE);
        myPoller = new HandControlPoller();
    }

    /**
     * Performs all ActionEvents associated with buttons and radio
     * button selections.
     * 
     * @param ae    the ActionEvent triggered
     */
    public void actionPerformed(ActionEvent ae)
    {
        Object source = ae.getSource();
        
        // actions performed by the train's "Running" button
        if(source == goStopButton)
        {
            // the train is set on a toggle with a corresponding boolean
            if(running)
            {
                running = false;
                train.stop();
//                System.out.println("train stopped");
            }
            else
            {
                running = true;
                // the default route for cycle one is clockwise
                if(routeDir.equals("CW"))
                {
                    train.setRoute(Direction.CLOCKWISE);
                }
                else
                {
                    train.setRoute(Direction.COUNTERCLOCKWISE);
                }
                // the train thread is set
                Thread trainThread = new Thread(train);
                trainThread.setName("train");
                //trainThread.setPriority(8);
                train.setSpeed((Integer)speedSpinner.getValue());
                trainThread.start();
//                System.out.println("train going " + speedSpinner.getValue());
            }
        }

        // the Hand Control indicators
        if(source == hc1Button || source == hc2Button)
        {
            // an integer variable indicates to the Listener whether one
            // or both or neither of the Hand Controls has been activated
            whichHandControl = 0;
            if(hc1Button.isSelected())
            {
                whichHandControl = 1;
                if(hc2Button.isSelected())
                {
                    whichHandControl = 3;
                }
            }
            if(hc2Button.isSelected())
            {
                whichHandControl = 2;
                if(hc1Button.isSelected())
                {
                    whichHandControl = 3;
                }
            }
        }

        // the route is clockwise
        if(source == routeCW)
        {
            routeDir = "CW";
        }

        // the route is counterclockwise
        if(source == routeCCW)
        {
            routeDir = "CCW";
        }
    }

    /**
     * The stateChanged method listens for change events.
     * 
     * @param ce    the ChangeEvent triggering the action
     */
    public void stateChanged(ChangeEvent ce)
    {
        // if the speedSpinner changes, change the speed of the train
        if(ce.getSource() == speedSpinner)
        {
            train.setSpeed((Integer)speedSpinner.getValue());
//            System.out.println("train going " + speedSpinner.getValue());
        }
    }

    /**
     * Handles the posting of error Messages from the Monitor.
     * 
     * @param msg   a Message containing an error
     */
    public void postErrorMessage(Message msg)
    {
        System.out.println(msg);
    }

    /**
     * Handles the posting of Sensor Messages from the Monitor.
     * 
     * @param msg   a Message regarding a Sensor
     */
    public void postSensorMessage(Message msg)
    {
        // will appear as text in the Sensor area
        sensorText.append(toStringSensor(msg) + "\n");
    }

    /**
     * Handles the posting of MOK Messages from the Monitor.
     * 
     * @param msg   a Message containing an MOK verification
     */
    public void postMOKMessage(Message msg)
    {
        System.out.println(msg);
    }

    /**
     * Handles the posting of Block Messages from the Monitor.
     * This method is unused in cycle 1.
     * 
     * @param msg   a Message containing Block information
     */
    public void postBlockMessage(Message msg)
    {
        System.out.println(msg);
    }

    /**
     * Handles the posting of Switch Messages from the Monitor.
     * This method was tested, but unused in Cycle 1.
     * 
     * @param msg   a Message containing Switch information
     */
    public void postSwitchMessage(Message msg)
    {
        // a Switch Message means that the angle has changed
        // the angle is extracted from the Message
        String angle = msg.getArg(MessageArg.SWITCH_ANGLE);
        // the ID of the Switch is extracted from the Message
        int sid = msg.getId();
        
        // The Switch indicators are changed according to ID and angle
        if(sid==1)
        {
            switch1.setText("Switch 1:  " + angle);
        }
        else if(sid==2)
        {
            switch2.setText("Switch 2:  " + angle);
        }
        else if(sid==3)
        {
            switch3.setText("Switch 3:  " + angle);
        }
        else if(sid==4)
        {
            switch4.setText("Switch 4:  " + angle);
        }
        else if(sid==5)
        {
            switch5.setText("Switch 5:  " + angle);
        }
        else if(sid==6)
        {
            switch6.setText("Switch 6:  " + angle);
        }
        else
        {
            System.out.println("Invalid switch id has been received.");
        }
    }

    /**
     * Handles the posting of Hand Control Status.
     * This method is unused in Cycle 1.
     * 
     * @param msg   a Message containing the status of a Hand Controller
     */
    public void postHandControlStatus(Message msg)
    {
        System.out.println(msg);
    }

    /**
     * This method converts a Sensor message to a String for display in the
     * Sensor area in the Gui.
     * 
     * @param msg   a Message containing the status of a Sensor
     * @return      a String suitable for viewing
     */
    public String toStringSensor(Message msg)
    {
        if(msg.getArg(MessageArg.SENSOR_VALUE_TWO) == null)
        {
            return "Sensor " + msg.getArg(MessageArg.SENSOR_VALUE_ONE);
        }
        else
        {
            return "Sensor " + msg.getArg(MessageArg.SENSOR_VALUE_ONE)
                + " and " + msg.getArg(MessageArg.SENSOR_VALUE_TWO);
        }
    }
}